package cn.ava.ymcc.service.impl;

import cn.ava.ymcc.constants.CommonConstants;
import cn.ava.ymcc.domain.KillActivity;
import cn.ava.ymcc.domain.KillCourse;
import cn.ava.ymcc.mapper.KillActivityMapper;
import cn.ava.ymcc.service.IKillActivityService;
import cn.ava.ymcc.service.IKillCourseService;
import cn.ava.ymcc.utils.AssertUtil;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ava
 * @since 2022-08-23
 */
@Service
@Slf4j
public class KillActivityServiceImpl extends ServiceImpl<KillActivityMapper, KillActivity> implements IKillActivityService {

    @Autowired
    private IKillCourseService killCourseService;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 秒杀活动发布,放入到redisSession,设置信号量
     * @param activityId
     */
    @Override
    @Transactional
    public void publish(Long activityId) {
        // JSR303参数非空校验
        // 发布秒杀活动业务校验: 活动是否存在.状态是否为待发布,时间是否已经结束
        KillActivity killActivity = selectById(activityId);
        AssertUtil.isNotNull(killActivity,"活动不存在!");
        boolean isWait = CommonConstants.KillCourseState.STATE_WAIT_PUBLISH.equals(killActivity.getPublishStatus());
        AssertUtil.isTrue(isWait,"活动状态异常!");
        // 当前时间必须在结束时间之前
        Date now = new Date();
        boolean before = now.before(killActivity.getEndTime());
        AssertUtil.isTrue(before,"活动已经结束啦!");
        // 发布活动将所有秒杀课程存入redis中
        Wrapper<KillCourse> query = new EntityWrapper<>();
        query.eq("activity_id", activityId);
        List<KillCourse> killCourses = killCourseService.selectList(query);
        AssertUtil.isFalse(killCourses.isEmpty(),"当前活动暂无秒杀课程,请先添加后再进行发布!");
        // 遍历所有课程,发布到redis缓存
        for (KillCourse killCourse : killCourses) {
            // 设置信号量
            String semaphoreKey = activityId + ":" + killCourse.getId();
            RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);
            boolean success = semaphore.trySetPermits(killCourse.getKillCount());
            // 设置成功,把秒杀商品存入redis中
            if (success) {
                String activityKey = "activity:" + activityId;
                // 使用hash结构存入到redis中
                redisTemplate.opsForHash().put(
                        activityKey, // 活动主key
                        killCourse.getId().toString(), // 秒杀商品key
                        killCourse // 商品本身
                );
                killCourse.setPublishTime(now);
                killCourse.setPublishStatus(CommonConstants.KillCourseState.STATE_PUBLISH);
                killCourseService.updateById(killCourse);
            }else {
                // 失败 兜底方案,通知运维,客服处理
                // 保存异常日志记录
                log.info("添加秒杀商品失败\t{}",killCourse);
            }
            // 修改活动状态
            killActivity.setPublishTime(now);
            killActivity.setPublishStatus(CommonConstants.KillCourseState.STATE_PUBLISH);
            updateById(killActivity);
        }

    }


    /**
     * 定时发布秒杀活动: 每天凌晨两点发布
     */
    @Scheduled(cron = "0 0 2 * * ?")
    void autoPublish(){
        log.info("定时发布秒杀活动开始");
        // 查询出状态为待发布且结束时间大于当前时间两天的活动
        Wrapper<KillActivity> query = new EntityWrapper<>();
        // 查询待发布活动
        query.eq("publish_status", CommonConstants.KillCourseState.STATE_WAIT_PUBLISH);
        // 获取当前时间
        Date now = new Date();
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(now);
        // 把日期往后增加二天
        calendar.add(Calendar.DATE, 2);
        // 这个时间就是日期往后推二天的结果
        now = calendar.getTime();
        // 格式化时间
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 格式化时区
        format.setTimeZone(SimpleTimeZone.getTimeZone("GMT+8"));
        // 得到后两天的时间
        String dateFormat = format.format(now);
        // 与数据库作比较,结束时间必须大于当前时间
        query.gt("end_time",dateFormat);
        // 查询出状态为待发布且结束时间为后两天的秒杀活动
        List<KillActivity> killActivities = selectList(query);
        // 发布活动
        killActivities.stream().map(KillActivity::getId)
                .forEach(this::publish);
        log.info("定时发布秒杀活动结束");
    }


    /**
     * 定时任务:每天凌晨三点扫描redis中过期课程数据,将其移出
     */
    @Scheduled(cron = "0 0 3 * * ?")
    void autoDeleteExpiredCourse(){
        Date now = new Date();
        log.info("定时删除过期课程开始\n{}",now.toLocaleString());
        List<KillCourse> killCourse = new ArrayList<>();
        //获取到所有活动下的值
        redisTemplate.keys("activity:*").forEach(key ->
                {
                    List values = redisTemplate.opsForHash().values(key);
                    killCourse.addAll(values);
                    // 遍历所有课程
                    killCourse.forEach(k ->{
                        // 判断当前时间是否大于结束时间
                        if (now.after(k.getEndTime())){
                            // 删除当前活动下的秒杀课程字段
                            redisTemplate.opsForHash().delete(key,k.getId().toString());
                            log.info("删除过期课程\n{}",k.getCourseName());
                        }
                    });
                });
    }


}
